package com.hmx.jpa;

import org.hibernate.jpa.QueryHints;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.persistence.*;
import java.util.List;

/**
 * @program: SpringDataJPA
 * @description:
 * @author: hmx
 * @create: 2021-07-11 14:56
 **/
public class JPATest {
    private EntityManagerFactory entityManagerFactory;
    private EntityManager entityManager;
    private EntityTransaction transaction;

    /**
     * 可以使用 JPQL 完成 UPDATE 和 DELETE 操作。
     */
    @Test
    public void testExecuteUpdate(){
        String jpql = "UPDATE Customer c SET c.lastName = ?0 WHERE c.id = ?1";
        Query query = entityManager.createQuery(jpql).setParameter(0, "YYY").setParameter(1, 18);
        query.executeUpdate();
    }

    @Test
    public void testJpqlFunction(){
        String jpql = "SELECT upper(c.email) FROM Customer c";
        List<String> emails  = entityManager.createQuery(jpql).getResultList();
        System.out.println(emails);
    }

    @Test
    public void testSubQuery(){
        //查询所有 Customer 的lastName为YY的 Order
        String jpql = "SELECT o FROM Order o " +
                "WHERE o.customer = (SELECT c FROM Customer c WHERE c.lastName = ?0)";
        Query query = entityManager.createQuery(jpql).setParameter(0, "QQ");
        List<Order> orders  = query.getResultList();
        System.out.println(orders.size());
    }

    @Test
    public void testLeftOuterJoinFetch(){
        String jpql = "FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.id = ?0";
        Customer singleResult = (Customer) entityManager.createQuery(jpql).setParameter(0, 18).getSingleResult();
        System.out.println(singleResult.getLastName());
        System.out.println(singleResult.getOrders().size());
    }


    /**
     * 查询order数量大于2的那些Customer
     */
    @Test
    public void testGroupBy(){
        String jpql = "SELECT o.customer FROM Order o GROUP BY o.customer HAVING count(o.id) >= 2";
        List<Customer> customers = entityManager.createQuery(jpql).getResultList();
        System.out.println(customers);
    }

    @Test
    public void testOrderBy(){
        String jpql = "FROM Customer c WHERE c.age > ?0 ORDER BY c.age DESC";
        Query query = entityManager.createQuery(jpql);
        query.setParameter(0,1);
        List<Customer> customers = query.getResultList();
        System.out.println(customers.size());
    }

    /**
     * 使用Hibernate的查询缓存
     */
    @Test
    public void testQueryCache(){
        String jpql = "FROM Customer c WHERE c.age > ?0";
        Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE,true);
        query.setParameter(0,1);
        List<Customer> customers = query.getResultList();
        System.out.println(customers.size());

        query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE,true);
        query.setParameter(0,1);
        customers = query.getResultList();
        System.out.println(customers.size());
    }

    /**
     * 适用于本地SQL
     */
    @Test
    public void testNativeQuery(){
        String sql = "SELECT age FROM jpa_customers WHERE id = ?0";
        Query query = entityManager.createNativeQuery(sql).setParameter(0, 3);
        Object result = query.getSingleResult();
        System.out.println(result);
    }

    /**
     * 适用于在实体类前使用 @NamedQuery 标记的查询语句
     */
    @Test
    public void testNamedQuery(){
        Query query = entityManager.createNamedQuery("testNamedQuery").setParameter(0, 3);
        Customer customer = (Customer) query.getSingleResult();
        System.out.println(customer);
    }

    /**
     * 默认情况下，若只查询部分属性，则将返回Object[] 类型的结果。 或者 Object[] 类型的 List。
     * 也可以在实体类中创建对应的构造器，然后再JPQL 语句中利用对应的构造器返回实体类的对象。
     */
    @Test
    public void testPartlyProperties(){
        String jpql = "SELECT new Customer(c.lastName,c.age) FROM Customer c WHERE c.id > ?0";
        List result = entityManager.createQuery(jpql).setParameter(0, 1).getResultList();
        System.out.println(result);
    }


    @Test
    public void testHelloJPQL(){
        String jpql = "FROM Customer c WHERE c.age > ?0";
        Query query = entityManager.createQuery(jpql);
        query.setParameter(0,1);
        List<Customer> customers = query.getResultList();
        System.out.println(customers.size());
    }

    @Test
    public void testSecondLevelCache(){
        Customer customer = entityManager.find(Customer.class, 1);
        transaction.commit();
        entityManager.close();
        entityManager = entityManagerFactory.createEntityManager();
        transaction = entityManager.getTransaction();
        transaction.begin();
        Customer customer2 = this.entityManager.find(Customer.class, 1);

    }

    /**
     * 对于关联的集合对象,默认使用懒加载的策略。
     * 使用维护关系的一方获取，还是使用不维护关联关系的一方获取，SQL语句相同。
     */
    @Test
    public void testManyToManyFind(){
        /*Item item = entityManager.find(Item.class,5);
        System.out.println(item.getItemName());

        System.out.println(item.getCategories().size());*/

        Category category = entityManager.find(Category.class, 7);
        System.out.println(category.getCategoryName());
        System.out.println(category.getItems().size());
    }

    @Test
    public void testManyToManyPersistence(){
        Item i1 = new Item();
        i1.setItemName("i-1");

        Item i2 = new Item();
        i2.setItemName("i-2");

        Category c1 = new Category();
        c1.setCategoryName("C-1");

        Category c2 = new Category();
        c2.setCategoryName("C-2");

        //设置关联关系
        i1.getCategories().add(c1);
        i1.getCategories().add(c2);

        i2.getCategories().add(c1);
        i2.getCategories().add(c2);

        c1.getItems().add(i1);
        c1.getItems().add(i2);

        c2.getItems().add(i1);
        c2.getItems().add(i2);

        //执行保存
        entityManager.persist(i1);
        entityManager.persist(i2);
        entityManager.persist(c1);
        entityManager.persist(c2);


    }

    /**
     * 1. 默认情况下，若获取不维护关联关系的一方，则也会通过左外连接获取其关联关系的对象。
     * 可以通过 @OneToOne 的fetch属性来修改加载策略。但依然会再发送SQL语句来初始化其关联的对象
     * 这说明在不维护关联关系的一方，不建议修改fetch属性。
     */
    @Test
    public void testOneToOneFind2(){
        Manager manager = entityManager.find(Manager.class, 3);
        System.out.println(manager.getMgrName());
        System.out.println(manager.getDept().getClass().getName());
    }

    /**
     * 1. 默认情况下，若获取维护关联关系的一方，则会通过左外连接获取其关联的对象
     * 但可以通过 @OneToOne的 fetch属性来修改加载策略
     */
    @Test
    public void testOneToOneFind(){
        Department dept = entityManager.find(Department.class, 4);
        System.out.println(dept.getDeptName());
        System.out.println(dept.getMgr().getClass().getName());
    }

    /**
     * 双向1-1的关联关系, 建议先保存不维护关联关系的一方， 既没有外键的一方， 这样不会多出 UPDATE 语句。
     */
    @Test
    public void testOneToOnePersistence(){
        Manager manager = new Manager();
        manager.setMgrName("M-AA");

        Department department = new Department();
        department.setDeptName("D-AA");

        //设置关联关系
        manager.setDept(department);
        department.setMgr(manager);

        //执行保存操作
        entityManager.persist(manager);
        entityManager.persist(department);
    }


    /**
     * 若是双向 1-n 的关联关系， 执行保存时
     * 若先保存 n 的一端， 再保存1的一端，默认情况下，会多出n 条 UPDATE语句。
     * 若先保存 1 的一端，则会多出n 条 UPDATE 语句
     * 在进行双向 1- n 关联关系时，建议使用n 的一方来维护关联关系，而1的一方不维护关联关系，这样会有效地减少SQL语句。
     * 注意: 若在1的一端的 @OneToMany 中使用 mappedBy 属性， 则 @OneToMany端就不能在使用 @JoinColumn属性了
     */

    @Test
    public void testUpdate(){
        Customer customer = entityManager.find(Customer.class, 18);
        customer.getOrders().iterator().next().setOrderName("0-XXX-18");
    }

    /**
     * 默认情况下，若删除1的一端，则会先把关联的n的一端的外键置空，然后进行删除。
     * 可以通过@OneToMany 的 cascade 属性来修改默认的删除策略。
     */
    @Test
    public void testOneToManyRemove(){
        Customer customer = entityManager.find(Customer.class, 15);
        entityManager.remove(customer);
    }

    /**
     * 默认对关联的多的一方使用懒加载的加载策略。
     * 可以使用 @OneToMany 的 fetch属性修改默认的加载策略
     */
    @Test
    public void testOneToManyFind(){
        Customer customer = entityManager.find(Customer.class, 15);
        System.out.println(customer.getLastName());
        System.out.println(customer.getOrders().size());
    }

    /**
     * 单向1-n 关联关系执行保存时，一定会多出 UPDATE 语句。
     * 因为 n 的一端在插入时不会同时插入外键列。
     */
    @Test
    public void testOneToManyPersist(){
        Customer customer = new Customer();
        customer.setAge(18);
        customer.setEmail("zz@163.com");
        customer.setLastName("ZZ");

        Order order1 = new Order();
        order1.setOrderName("O-ZZ-1");

        Order order2 = new Order();
        order2.setOrderName("O-ZZ-2");

        //建立关联关系
        customer.getOrders().add(order1);
        customer.getOrders().add(order2);

        //执行保存操作
        entityManager.persist(customer);
        entityManager.persist(order1);
        entityManager.persist(order2);
    }

    @Test
    public void testManyToOneUpdate(){
        Order order = entityManager.find(Order.class, 7);
        //order.getCustomer().setLastName("FFF");
    }


    /**
     * 不能直接删除1的一端，因为有外键约束
     */
    @Test
    public void testManyToOneRemove(){
        Customer customer = entityManager.find(Customer.class, 7);
        entityManager.remove(customer);
    }

    /**
     * 默认情况下，使用左外连接的方式来获取n的一端的对象和其关联的1的一端的对象。
     * 可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
     */
    @Test
    public void testManyToOneFind(){
        Order order = entityManager.find(Order.class, 7);
        System.out.println(order.getOrderName());
        //System.out.println(order.getCustomer().getLastName());
    }

    /**
     * 保存多对一时,建议先保存1的一端，后保存n的一端，这样不会多出额外的UPDATE语句。
     */
    @Test
    public void testManyToOnePersist(){
        Customer customer = new Customer();
        customer.setAge(18);
        customer.setEmail("ff@163.com");
        customer.setLastName("FF");

        Order order1 = new Order();
        order1.setOrderName("O-FF-1");

        Order order2 = new Order();
        order2.setOrderName("O-FF-2");

        //设置关联关系
        //order1.setCustomer(customer);
        //order2.setCustomer(customer);

        //执行保存操作
        entityManager.persist(customer);
        entityManager.persist(order1);
        entityManager.persist(order2);
    }

    @Test
    public void testRefresh(){
        Customer customer = entityManager.find(Customer.class, 1);
        customer = entityManager.find(Customer.class, 1);
        entityManager.refresh(customer);
    }

    @Test
    public void testFlush(){
        Customer customer = entityManager.find(Customer.class, 1);
        System.out.println(customer);
        customer.setLastName("AA");
        entityManager.flush();
    }

    /**
     * 总的来说: 类似于 hibernate Session 的 saveOrUpdate方法。
     *     1. 若传入的是一个临时对象
     *     会创建一个新的对象，把临时对象的属性复制到新的对象中，然后对新的对象执行持久化操作。所以
     *     新的对象中有id，但以前的对象中没有id。
     */
    @Test
    public void testMerge1(){
        Customer customer = new Customer();
        customer.setAge(18);
        customer.setEmail("cc@163.com");
        customer.setLastName("CC");
        Customer customer2 = entityManager.merge(customer);
        System.out.println(customer.getId());
        System.out.println(customer2.getId());
    }

    /**
     * 若传入的是一个游离对象，即传入的对象有OID.
     * //1. 若在EntityManager缓存中没有该对象
     * //2. 若在数据库中也没有对应的记录
     * //3. JPA 会创建一个新的对象，然后把当前游离对象的属性复制到新创建的对象中。
     * //4. 对新创建的对象执行insert操作
     */
    @Test
    public void testMerge2(){
        Customer customer = new Customer();
        customer.setAge(18);
        customer.setEmail("cc@163.com");
        customer.setLastName("CC");
        customer.setId(100);
        Customer customer2 = entityManager.merge(customer);
        System.out.println(customer.getId());
        System.out.println(customer2.getId());
    }

    /**
     * 若传入的是一个游离对象，即传入的对象有OID.
     * //1. 若在EntityManager缓存中没有该对象
     * //2. 若在数据库中有对应的记录
     * //3. JPA 会查询对应的记录，然后返回该记录的一个对象，再然后会把游离对象的属性复制到查询的对象中。
     * //4. 对新创建的对象执行insert操作
     */
    @Test
    public void testMerge3(){
        Customer customer = new Customer();
        customer.setAge(18);
        customer.setEmail("ee@163.com");
        customer.setLastName("EE");
        customer.setId(4);
        Customer customer2 = entityManager.merge(customer);
        System.out.println(customer == customer2);
    }

    /**
     * 若传入的是一个游离对象，即传入的对象有OID.
     * //1. 若在EntityManager缓存中有该对象
     * //2. JPA 会把游离对象的属性复制到查询到的EntityManager 缓存中的对象中。
     * //3. EntityManager缓存中的对象执行UPDATE;
     */
    @Test
    public void testMerge4(){
        Customer customer = new Customer();
        customer.setAge(18);
        customer.setEmail("dd@163.com");
        customer.setLastName("DD");
        customer.setId(4);
        Customer customer1 = entityManager.find(Customer.class, 4);
        entityManager.merge(customer);
        System.out.println(customer == customer1);
    }

    @Before
    public void init(){
        entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
        entityManager = entityManagerFactory.createEntityManager();
        transaction = entityManager.getTransaction();
        transaction.begin();
    }

    @Test
    public void testFind(){
        Customer reference = entityManager.getReference(Customer.class, 1);
        System.out.println(reference);
        System.out.println(reference.getClass().getName());
        System.out.println("-------------");
        Customer customer = entityManager.find(Customer.class, 1);
        System.out.println(customer);
    }

    /**
     *     使对象由临时状态变为持久状态
     *     如果对象有id，则不能执行insert操作，而会抛出异常
     */
    @Test
    public void testPersistence(){
        Customer customer = new Customer();
        customer.setAge(15);
        customer.setEmail("kuang@qq.com");
        customer.setLastName("BB");
        /*customer.setId(3)*/;
        entityManager.persist(customer);
    }

    /**
     * 类似于 hibernate的delete方法。把对象对应的记录从数据库中移除
     * 该方法只能移除持久化对象。而hibernate的delete方法实际上还可以移除游离对象
     */
    @Test
    public void testRemove(){
//        Customer customer = new Customer();
//        customer.setId(2);
        Customer customer = entityManager.find(Customer.class, 2);
        entityManager.remove(customer);
    }

    @After
    public void destroy(){
        transaction.commit();
        entityManager.close();
        entityManagerFactory.close();
    }
}
